package org.xtlgen;

import java.io.File;
import java.io.IOException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException; 

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JOptionPane;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.SpringLayout;
import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Dimension;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * Main frame for the XtlGen system.
 */
public class XtlGenFrame extends JFrame {

  private XtlGenDisplay graphicsDisplay;
  private JFileChooser fileChooser;
  private JButton reload;
  private JButton export;
  private XMLFileFilter xmlFileFilter;
  private File currentFile;
  private DrawingArea drawingArea;
  
  /**
   * Create and layout components. Link components to event handler methods.
   */
  public XtlGenFrame(String fileName) {
    super("XtlGen");

    setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

    Container mainPanel = getContentPane();

    ////////////////////////
    // Create
    ////////////////////////
    
    JButton load = new JButton("Load...");
    reload = new JButton("Reload");
    export = new JButton("Export...");

    reload.setEnabled(false);
    export.setEnabled(false);
    
    graphicsDisplay = new XtlGenDisplay();
    fileChooser = null;
    xmlFileFilter = null;
    currentFile = new File(fileName == null ? "" : fileName);
    drawingArea = null;
    
    ////////////////////////
    // Layout
    ////////////////////////
    
    // Put the buttons on a separate panel to simplify layout
    GridLayout buttonLayout = new GridLayout(0,1);
    JPanel buttonPanel = new JPanel(buttonLayout);
    buttonLayout.setVgap(5);
    buttonPanel.add(load);
    buttonPanel.add(reload);
    buttonPanel.add(export);
    
    // Prevent buttons from stretching
    buttonPanel.setMaximumSize(buttonPanel.getPreferredSize());

    
    JScrollPane scroller = new JScrollPane(graphicsDisplay);
    scroller.setPreferredSize(new Dimension(500, 500));

    SpringLayout mainLayout = new SpringLayout();
    mainPanel.setLayout(mainLayout);
    
    mainPanel.add(scroller);
    mainPanel.add(buttonPanel);

    mainLayout.putConstraint(SpringLayout.WEST, scroller,
                             0,
                             SpringLayout.WEST, mainPanel);
    mainLayout.putConstraint(SpringLayout.NORTH, scroller,
                             0,
                             SpringLayout.NORTH, mainPanel);
    
    mainLayout.putConstraint(SpringLayout.WEST, buttonPanel,
                             5,
                             SpringLayout.EAST, scroller);
    mainLayout.putConstraint(SpringLayout.NORTH, buttonPanel,
                             5,
                             SpringLayout.NORTH, mainPanel);
    
    mainLayout.putConstraint(SpringLayout.EAST, mainPanel,
                             5,
                             SpringLayout.EAST, buttonPanel);

    mainLayout.putConstraint(SpringLayout.SOUTH, mainPanel,
                             0,
                             SpringLayout.SOUTH, scroller);
    
    ////////////////////////
    // Link event handlers
    ////////////////////////

    load.addActionListener(
      new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          loadPressed();
        }
      });

    reload.addActionListener(
      new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          reloadPressed();
        }
      });

    export.addActionListener(
      new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          exportPressed();
        }
      });

    //////////////////////////////////////////////
    // Load the file if given in the command line
    //////////////////////////////////////////////
    
    loadFile(this.currentFile);
  }

  /*
   * File filter for XML files, used in the file chooser
   */
  private class XMLFileFilter extends FileFilter
  {
    //Accept all directories and all xml files.
    public boolean accept(File f) {
      if (f.isDirectory()) {
        return true;
      }

      if (f.getName().endsWith(".xml")) {
        return true;
      }
      
      return false;
    }

    //The description of this filter
    public String getDescription() {
      return "XML files (*.xml)";
    }
  }
  
  /*
   * Load button action handler
   */
  private void loadPressed() {
    if (this.fileChooser == null) {
      this.fileChooser = new JFileChooser(currentFile);
      this.xmlFileFilter = new XMLFileFilter();
    }
    
    this.fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
    this.fileChooser.setFileFilter(xmlFileFilter);
    this.fileChooser.setDialogTitle("Select file");
    if (this.fileChooser.showDialog(this, "OK")  == JFileChooser.APPROVE_OPTION) {
      loadFile(this.fileChooser.getSelectedFile());
    }
  }    
  
  /*
   * Reload button action handler
   */
  private void reloadPressed() {
    loadFile(this.currentFile);
  }    
  
  /*
   * Export button action handler
   */
  private void exportPressed() {
    if (this.fileChooser == null) {
      this.fileChooser = new JFileChooser(currentFile);
      this.xmlFileFilter = new XMLFileFilter();
    }
    
    this.fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
    this.fileChooser.setFileFilter(this.fileChooser.getAcceptAllFileFilter());
    this.fileChooser.setDialogTitle("Select file");
    this.fileChooser.setSelectedFile(
      new File(currentFile.getPath().replaceFirst("(?i)\\.xml$", ".txt")));
    if (this.fileChooser.showDialog(this, "OK")  == JFileChooser.APPROVE_OPTION) {
      File out = this.fileChooser.getSelectedFile();
      if ((! out.exists()) ||
          (JOptionPane.showConfirmDialog(
             this, "File exists, overwrite?", "Confirm",
             JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) ) {
        FileCanvas canvas = new FileCanvas(out);
        drawingArea.draw(canvas);
        try {
          canvas.export();
        }
        catch(IOException e) {
          JOptionPane.showMessageDialog(this,
                                        e.getMessage(),
                                        "I/O error", JOptionPane.ERROR_MESSAGE);
        }
      }
    }
  }    
  
  /*
   * Load the given file, return true if successful
   */
  private void loadFile(File file) {
    drawingArea = readShapes(file);

    if (drawingArea != null) {
      this.graphicsDisplay.setDrawing(drawingArea);
      this.graphicsDisplay.repaint();
      this.setTitle("XtlGen - " + file.getPath());
      this.currentFile = file;
      this.reload.setEnabled(true);
      this.export.setEnabled(true);
    }
  }    
  
  /*
   * Read the shapes from the given file
   */
  private DrawingArea readShapes(File file) {
    DrawingArea result = null;
    
    try {
      DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
      DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
      Document doc = docBuilder.parse(file);

      Element docElem = doc.getDocumentElement();

      if (docElem.getNodeName().equals("DrawingArea")) {
        // normalize text representation
        docElem.normalize();

        result = new DrawingArea(docElem);
      }
      else {
        throw new IllegalArgumentException("Root element must be a DrawingArea");
      }
    }catch (SAXParseException err) {
      String msg =
        "Line " + err.getLineNumber () +
        ", uri " + err.getSystemId () + "\n" + err.getMessage ();
      JOptionPane.showMessageDialog(this,
                                    msg,
                                    "Parsing error", JOptionPane.ERROR_MESSAGE);
    }catch (Throwable t) {
      JOptionPane.showMessageDialog(this,
                                    t.getMessage(),
                                    "Parsing error", JOptionPane.ERROR_MESSAGE);
    }

    return result;
  }
}